package com.ruoyi.file.service.impl;

import com.ruoyi.file.bean.FileUploadResult;
import com.ruoyi.file.utils.*;
import io.minio.*;
import io.minio.messages.Item;
import com.ruoyi.file.config.MinioConfig;
import com.ruoyi.file.controller.SysFileController;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Minio 文件存储。
 *
 * @author william.zhao and zhao xiang.
 * @version 1.0
 * @date 2023/05/05
 * @since jdk-17.0.2
 */
@Primary
@Service
public class MinioSysFileServiceImpl extends AbstractFileService {
    private static final Logger log = LoggerFactory.getLogger(SysFileController.class);

    @Autowired
    private MinioConfig minioConfig;

    @Autowired
    private MinioClient client;
    /** the date format is: yyyy/MM/dd/ */
    private final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd/");


    /**
     * Minio文件上传接口。
     *
     * @param file 上传的文件。
     * @return the upload result, result[0] means the url, result[1] means the file path.
     * @throws Exception
     */
    @Override
    public FileUploadResult uploadFile(MultipartFile file) throws Exception {
        // String fileName = FileUploadUtils.extractFilename(file);
        // the file original name would have some trouble when review the url, so rename it.
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        String fileName = UUIDHelper.get36UUID() + "." + extension;
        fileName = dateFormat.format(new Date()) + fileName;
        log.info("fileName: {}", fileName);
        String bucketName = minioConfig.getBucketName();
        InputStream inputStream = file.getInputStream();

        PutObjectArgs args = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .stream(inputStream, file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        client.putObject(args);
        inputStream.close();
        String url = minioConfig.getReturnUrl() + "/" + minioConfig.getBucketName() + "/" + fileName;
        log.info("upload->{} to {}", file.getOriginalFilename(), url);
        FileUploadResult uploadResult = new FileUploadResult();
        uploadResult.setUrl(url);
        uploadResult.setFileName(fileName);
        String md5 = Md5Utils.getFileMD5(file.getInputStream());
        uploadResult.setFileMD5(md5);
        return uploadResult;
    }

    /**
     * Minio文件上传接口。
     *
     * @param file 上传的文件。
     * @return 访问地址。
     * @throws Exception
     */
    @Override
    public FileUploadResult uploadFileWithFileCode(String fileCode, MultipartFile file) throws Exception {
        String fileName = fileCode;
        InputStream inputStream = file.getInputStream();
        PutObjectArgs args = PutObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(fileName)
                .stream(inputStream, file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        client.putObject(args);
        inputStream.close();
        String url = minioConfig.getReturnUrl() + "/" + minioConfig.getBucketName() + "/" + fileName;
        log.info("upload->{} to {}", file.getOriginalFilename(), url);

        FileUploadResult uploadResult = new FileUploadResult();
        uploadResult.setFileName(fileName);
        uploadResult.setUrl(url);
        String md5 = Md5Utils.getFileMD5(file.getInputStream());
        uploadResult.setFileMD5(md5);
        return uploadResult;
    }

    /**
     * remove file by file path in server.
     *
     * @param filePath the file path in server, the filePath likes: 2023/07/06/blob_20230706112107A003.png
     * @return success or fail.
     */
    @Override
    public String removeFile(String filePath) {
        String flag = "success";
        try {
            log.info("remove file ->{}", filePath);
            RemoveObjectArgs args = RemoveObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    // filePath likes: 2023/07/06/blob_20230706112107A003.png
                    .object(filePath)
                    .build();
            client.removeObject(args);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            flag = "fail";
        }
        return flag;
    }

    /**
     * 判断桶中是否存在文件夹
     *
     * @param bucketName 同名称
     * @param objectName 文件夹名称
     * @param isDeep     是否递归查询(暂不支持)
     * @return
     */
    private Boolean checkBucketFolderExist(String bucketName, String objectName, Boolean isDeep) throws Exception {
        if (checkBucketExist(bucketName)) {
            Iterable<Result<Item>> results = client.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(isDeep).build());
            return results.iterator().hasNext(); // 文件夹下存在文件
        }
        return false;
    }

    /**
     * 创建文件夹
     *
     * @param bucketName 桶名
     * @param folderName 文件夹名称
     * @return
     * @throws Exception
     */
    private ObjectWriteResponse createBucketFolder(String bucketName, String folderName) throws Exception {
        if (!checkBucketExist(bucketName)) {
            throw new RuntimeException("必须在桶存在的情况下才能创建文件夹");
        }
        if (!StringUtils.hasLength(folderName)) {
            throw new RuntimeException("创建的文件夹名不能为空");
        }
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(folderName + "/")
                .stream(new ByteArrayInputStream(new byte[0]), 0, 0)
                .build();
        return client.putObject(putObjectArgs);
    }

    @Override
    public FileUploadResult uploadFileWithFileFolder(String folder, MultipartFile file) throws Exception {
        if (!checkBucketFolderExist(minioConfig.getBucketName(), folder, true)) {
            try {
                createBucketFolder(minioConfig.getBucketName(), folder);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        String fileName = dateFormat.format(new Date()) + UUIDHelper.get36UUID() + "." + extension;

        // the original file name may cause review issue.
        // String fileName = file.getOriginalFilename();
        InputStream fileStream = file.getInputStream();
        try {
            String contentType = ViewContentType.getContentType(fileName);
            log.info("上传的文件{}类型为{}", fileName, contentType);
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(folder + "/" + fileName)
                    .stream(fileStream, file.getSize(), -1)
                    .contentType(contentType)
                    //.contentType(file.getContentType())
                    .build();
            client.putObject(args);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException("minio上传文件错误");
        } finally {
            if (fileStream != null) {
                fileStream.close();
            }
        }
        String url = minioConfig.getReturnUrl() + "/" + minioConfig.getBucketName() + "/" + folder + "/" + fileName;
        log.info("upload->{} to {}", file.getOriginalFilename(), url);

        FileUploadResult uploadResult = new FileUploadResult();
        uploadResult.setFileName(fileName);
        uploadResult.setUrl(url);
        uploadResult.setFileMD5(Md5Utils.getFileMD5(file.getInputStream()));
        return uploadResult;
    }

    @Override
    public FileUploadResult uploadFileByPath(String filePath, MultipartFile file) throws Exception {
        String folder = filePath.substring(0, filePath.indexOf("/"));
        if (!checkBucketFolderExist(minioConfig.getBucketName(), folder, true)) {
            try {
                createBucketFolder(minioConfig.getBucketName(), folder);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // the original file name may cause review issue.
        // String fileName = file.getOriginalFilename();
        InputStream fileStream = file.getInputStream();
        try {
            String contentType = ViewContentType.getContentType(filePath);
            log.info("上传的文件{}类型为{}", filePath, contentType);
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(filePath)
                    .stream(fileStream, file.getSize(), -1)
                    .contentType(contentType)
                    //.contentType(file.getContentType())
                    .build();
            client.putObject(args);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException("minio上传文件错误");
        } finally {
            if (fileStream != null) {
                fileStream.close();
            }
        }
        String url = minioConfig.getReturnUrl() + "/" + minioConfig.getBucketName() + "/" + filePath;
        log.info("upload->{} to {}", filePath, url);

        FileUploadResult uploadResult = new FileUploadResult();
        uploadResult.setFileName(CscpFileUtils.getName(url));
        uploadResult.setFilePath(filePath);
        uploadResult.setUrl(url);
        // cost time but no need so comment out the code.
        // uploadResult.setFileMD5(Md5Utils.getFileMD5(file.getInputStream()));
        return uploadResult;
    }


    /**
     * 预览
     *
     * @param fileName
     * @param response
     */
    @Override
    public void preview(String fileName, HttpServletResponse response) {
        InputStream file = null;
        FileInputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            Assert.isTrue(StringUtils.hasLength(fileName), "预览的路径不能为空");
            file = client.getObject(GetObjectArgs.builder().bucket(minioConfig.getBucketName()).object(fileName).build());
            File newFile = new File(fileName);
            FileUtils.copyInputStreamToFile(file, newFile);
            inputStream = new FileInputStream(newFile);
            outputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, len);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * get file inputstream by file path in server.
     *
     * @param filePath the file path in server.
     * @return the inputstream.
     */
    @Override
    public InputStream getInputStreamByPath(String filePath) {
        try {
            InputStream file = client.getObject(GetObjectArgs.builder().bucket(minioConfig.getBucketName()).object(filePath).build());
            return file;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    private final VaipFileUtil fu = new VaipFileUtil();
    private final VaipFileContentUtil fcu = new VaipFileContentUtil();

    /**
     * get content by url.
     *
     * @param fileURL the file url.
     * @return the fine content.
     */
    @Override
    public String getContentByURL(String fileURL, String extension) {
        String suffix = extension;
        if (!StringUtils.hasLength(extension)) {
            suffix = fu.getSuffix(fileURL);
        }

        try {
            URL url = new URL(fileURL);
            URLConnection connection = url.openConnection();
            InputStream inputStream = connection.getInputStream();
            String content = fcu.getFileContent(inputStream, suffix);
            inputStream.close();

            return content;
        } catch (Exception e) {
            log.error("no file! message:{}, url:{}", e.getMessage(), fileURL);
        }
        return null;
    }

    /**
     * 检查桶是否存在
     *
     * @param bucketName 桶名称
     * @return boolean true-存在 false-不存在
     */
    private boolean checkBucketExist(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("检测桶的时候，桶名不能为空！");
        }
        return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * get file list under special path.
     *
     * @param path the file path in server
     * @return the file path list under a special path.
     */
    @Override
    public List<String> getFileListUnberPath(String path) {
        // the "/" is MUST!
        path = path.endsWith("/") ? path : path + "/";
        List<String> pathList = new ArrayList();
        Iterable<Result<Item>> itemLists =
                client.listObjects(ListObjectsArgs.builder().bucket(minioConfig.getBucketName()).prefix(path).recursive(false).build());
        try {
            for (Result<Item> result : itemLists) {
                Item item = result.get();
                // the object name is the file path in bucket name, like: aaa/bbb/ccc/ddd.docx
                pathList.add(item.objectName());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return pathList;
    }

}
